import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.Random;
import java.util.HashMap;
import java.util.TreeSet;

public class PlotSurf {
	
	private NeuralNet substrate;
	private NeuralNet cppn;
	private static TestData[] data;
	private static double[][][] training;
	
	public PlotSurf(String[][] layoutS, String[][] layoutC, double of1, double of2) {
		substrate = new NeuralNet(NeuralNet.quickSubstrate(layoutS), 0);
		cppn = new NeuralNet(NeuralNet.quickCPPN(layoutC), 0);
		
		//Part of the old system, not really necessary, but still used for tests
		data = 	data();
		training = training(1);
   
    cppn.getNode(0,1).setWeight(0, 0.0); 
    cppn.getNode(0,1).setWeight(1, 0.0); 
    cppn.getNode(0,2).setWeight(0, 0.0); 
    cppn.getNode(0,2).setWeight(1, 0.0); 
    cppn.getNode(0,3).setWeight(0, 0.0); 
    cppn.getNode(0,3).setWeight(1, 0.0); 
    cppn.getNode(0,4).setWeight(0, 0.0); 
    cppn.getNode(0,4).setWeight(1, 0.0); 
    
    cppn.getNode(0,0).setWeight(0, 0.0); 
    cppn.getNode(0,0).setWeight(1, 0.0); 
    cppn.addConnection(0,0,2,0);
    cppn.getNode(0,0).setWeight(2, -1.3);

    cppn.getNode(0,5).setWeight(0, 3.0); 
    cppn.getNode(0,5).setWeight(1, 0.0); 
    cppn.getNode(0,6).setWeight(0, 0.0); 
    cppn.getNode(0,6).setWeight(1, 3.0);

    cppn.getNode(1,0).setWeight(0, 1.0); 
    cppn.getNode(1,1).setWeight(0, 1.0);
    
    setSubstrateWeights(cppn, substrate);
    setTraining();
    //System.out.println("Init: "+substrate.evaluate(training) / training.length);
    //checkTraining();
    
    cppn.getNode(0,0).setWeight(0, cppn.getNode(0,0).getWeight(0) + of1); 
    cppn.getNode(0,0).setWeight(1, cppn.getNode(0,0).getWeight(1) + of2); 

	}

	public static void main(String[] args) {
		String[][] layoutS = {{"11","11","L"},{"11","11","S"}};
		//String[][] layoutC = {{"B", "L", "L", "L", "L"},{"S", "L"}, {"G", "G"}, {"G", "G"}, {"S"}};
		String[][] layoutC = {{"B", "L", "L", "L", "L", "L", "L"}, {"G", "G"}, {"SS"}};
		BufferedWriter w = null;
		try {
      w = new BufferedWriter(new FileWriter("surf5.csv"));
    } catch (IOException ex) {
      System.err.println(ex.getMessage());
    }
		
		double[] off = new double[26];
		double start = 0;
		double incr = 0.1;
		for (int i = 0; i < off.length; i++) {
			off[i] = start + i*incr;
		}
		
		for (int i = 25; i < off.length; i++) {
			for (int j = 0; j < off.length; j++) {
				PlotSurf net = new PlotSurf(layoutS, layoutC, off[i], off[j]);
				System.out.println(off[i]+ ", "+ off[j]);
				
				double[][][] cppnData = net.cppnData(net.substrate);
				double e = Double.POSITIVE_INFINITY;
				double ep = 0;
				int k = 0;
				while (k<10000 && Math.abs(ep-e) > Math.pow(10, -9) && e > Math.pow(10, -6)) {
					ep = e;
					e = net.cppn.trainNetwork(cppnData, 0.1, 0.0) / cppnData.length;
					//System.out.println(k +"\t"+ e + "\t" + (ep-e));
					if (e > ep) System.err.println("CPPN learning rate fail!");
					k++;
			    }
				try {
					w.write(off[i]+ ", "+ off[j]+ ", "+e+"\n");
				} catch (IOException ex) {
					System.err.println(ex.getMessage());
				}
				System.out.println(e);
			}
	    try {
        w.flush();
	    } catch (IOException ex) {
	      System.err.println(ex.getMessage());
	    }
		}
		//setSubstrateWeights(net.cppn, net.substrate);
	    //net.interleaved(25);
	    try {
	      w.close();
	    } catch (IOException ex) {
	      System.err.println(ex.getMessage());
	    }
		//net.hillClimb(100);
		//net.boxWrite(training[600][0], "Res/input");
		//net.boxWrite(training[600][1], "Res/target");i
    
	    /*System.out.println("Train: "+net.substrate.evaluate(training) / training.length);
		System.out.println("\nAvg Dist :" + net.evaluate(net.substrate, 1));
		System.out.println("Avg Max 1 :" + net.evaluateMax(net.substrate));
		System.out.println("Avg Max 2 :" + net.evaluateMax2(net.substrate)+"\n");
    
		net.evaluateFull(net.substrate);*/

		
		//net.testBoxes();
		/*System.out.println("");
		net.boxesDistance(data);
		System.out.println("");
		net.boxesDistance(test);
		
		System.out.println("");
		double[] i = new double[1];
		double[] v = {1, Math.sqrt(2), 2, Math.sqrt(5), Math.sqrt(8), 3};
		for (int j=0; j < v.length; j++) {
			i[0] = v[j];
			net.cppn.processInput(i);
			System.out.println(net.cppn.getOutputs()[0]);
		}*/
	}
	
	public void interleaved(int thres) {
		int cppnFactor = 150;
		setSubstrateWeights(cppn, substrate);
		double e;
    double ex = substrate.evaluate(training) / training.length;
    double ep;
    int c = 0;
    long time = System.currentTimeMillis();
		do {
      ep = Double.POSITIVE_INFINITY;
			for (int i = 0; i < thres; i++) {
				substrate.trainNetwork(training, 10.0, 0);
				e = substrate.evaluate(training) / training.length;
        if (e > ep) System.err.println("Substrate learning rate fail!");
        ep = e;
				System.out.println("Substrate: "+ e);
        /*try {
          w.write(e +", "+ ex +"\n");
        } catch (IOException excp) {
          System.err.println(excp.getMessage());
        }*/
			}
			double[][][] cppnData = cppnData(substrate);
      e = ex;
			for (int i = 0; i < cppnFactor*thres; i++) {
        ep = e;
				e = cppn.trainNetwork(cppnData, 0.1, 0) / cppnData.length;
        if (e > ep) System.err.println("CPPN learning rate fail!");
				if (i == 0 || i == cppnFactor*thres-1) {
					System.out.println("CPPN: "+ e);			
        }
			}
      e = ex;
			setSubstrateWeights(cppn, substrate);
			ex = substrate.evaluate(training) / training.length;
      System.out.println(ex +"\t"+(e-ex));
      c++;
		} while (System.currentTimeMillis() - time < 1000*60*60*5);
	}
	
	public void hillClimb(int thres) {
		//Start at point in cppn space
		setSubstrateWeights(cppn, substrate);
		//Evaluate starting point
		double bestErr = evaluate(substrate, 9);
		System.out.println("Substrate error: "+bestErr);
		
		//Create substrate clone
		String[][][] con = new String[substrate.size()][][];
		for (int i = 0; i < substrate.size(); i++) {
			con[i] = new String[substrate.size(i)][3];
			for (int j = 0; j < substrate.size(i); j++) {
				Node n = substrate.getNode(i, j);
				con[i][j][0] = "" + n.getCoords()[0];
				con[i][j][1] = "" + n.getCoords()[1];
				con[i][j][2] = n.getType();
			}
		}
		NeuralNet mutation = new NeuralNet(con, 0);
		
		//Create cppn clone
		String[][][] cCon = new String[cppn.size()][][];
		for (int i = 0; i < cppn.size(); i++) {
			cCon[i] = new String[cppn.size(i)][3];
			for (int j = 0; j < cppn.size(i); j++) {
				Node n = cppn.getNode(i, j);
				cCon[i][j][0] = "" + n.getCoords()[0];
				cCon[i][j][1] = "" + n.getCoords()[1];
				cCon[i][j][2] = n.getType();
			}
		}
		NeuralNet cppnClone = new NeuralNet(cCon, 1);
		
		Random r = new Random();
    int count = 0;
		do {
			/*double[] mut = new double[substrate.size(0)];
			for (int i = 0; i < substrate.size(0); i++) {
				mut[i] = r.nextGaussian();
			}*/
			//Mutate current substrate weights
			for (int i = 0; i < substrate.size(); i++) {
				for (int j = 0; j < substrate.size(i); j++) {
					Node n = substrate.getNode(i, j);
					for (int k = 0; k < n.size(); k++) {
            if (r.nextDouble() > 0.5) {
						  double w = n.getWeight(k);
						  mutation.getNode(i, j).setWeight(k, w + 2*r.nextGaussian());
            }
					}
				}
			}
			//Copy master cppn weights
			for (int i = 0; i < cppn.size(); i++) {
				for (int j = 0; j < cppn.size(i); j++) {
					Node n = cppn.getNode(i, j);
					for (int k = 0; k < n.size(); k++) {
						cppnClone.getNode(i, j).setWeight(k, n.getWeight(k));
					}
				}
			}
      /*
      //Mutate CPPN directly
			for (int i = 0; i < cppn.size(); i++) {
				for (int j = 0; j < cppn.size(i); j++) {
					Node n = cppn.getNode(i, j);
					for (int k = 0; k < n.size(); k++) {
            //if (r.nextDouble() > 0.8) {
						  double w = n.getWeight(k);
						  cppnClone.getNode(i, j).setWeight(k, w + r.nextGaussian());
            //}
					}
				}
			}*/
			
			//Create cppn training data
			double[][][] cppnData = cppnData(mutation);
			//Fit cppnClone to mutation
			double cppnErr = Double.POSITIVE_INFINITY;
			for (int i = 0; i < thres; i++) {
				double temp = cppnClone.trainNetwork(cppnData, 0.01, 0) / cppnData.length;
				//Sanity check
				if (temp > cppnErr) {
					System.err.println("Warning: Error-rate diverging, lower learning rate");
				}
				cppnErr = temp;
				if (i == 0 || i == thres-1) {
					System.out.println("CPPN: "+ cppnErr);
				}
			}
			setSubstrateWeights(cppnClone, mutation);
			double newErr = evaluate(mutation, 9);
			System.out.println(newErr + " \t"+ bestErr +" \t"+count);
			if (newErr < bestErr) {
				//Swap cppns
				NeuralNet temp = cppn;
				cppn = cppnClone;
				cppnClone = temp;
				//Swap substrate
				temp = substrate;
				substrate = mutation;
				mutation = temp;
				
				bestErr = newErr;
        count = 0;
			} else {
        count++;
      }
			//cppnData = cppnData(substrate);
			//System.out.println("Zero: "+cppn.evaluate(cppnData)/cppnData.length);
		} while (bestErr > 1 && count < 5000);
	}
	
	//Note: No fix for bias nodes yet
	public double[][][] cppnData(NeuralNet net) {
		ArrayList<double[][]> d = new ArrayList<double[][]>();
		for (int i = 0; i < net.size(); i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[][] e = {{n.getCoords()[0], n.getCoords()[1], m.getCoords()[0],
							m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]},{n.getWeight(k)},{i}};
					//double[][] e = {{Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))}, {n.getWeight(k)},{i}};
					//double[][] e = {{n.getCoords()[0]-m.getCoords()[0],
					//    n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/},
					//		{n.getWeight(k)},{i}};
					d.add(e);
				}
			}
		}
		double[][][] r = new double[d.size()][][];
		return d.toArray(r);
	}
	
	//Use CPPN c to set the weights of the substrate net
	public static void setSubstrateWeights(NeuralNet c, NeuralNet net){
		for (int i = 0; i < net.size(); i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[] input = {n.getCoords()[0], n.getCoords()[1], 
							m.getCoords()[0], m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]};
					//double[] input = {Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))};
					//double[] input = {n.getCoords()[0]-m.getCoords()[0],
					//		n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/};
					
					c.processInput(input);
					n.setWeight(k, c.getOutputs()[i]);
				}
			}
		}
	}
	
	/*public void boxesDistance(double[][][] data) {
		HashMap<Double, Integer> store = new HashMap<Double, Integer>();
		for (int i = 0; i < data.length; i++) {
			substrate.processInput(data[i][0]);
			double[] o = substrate.getOutputs();
			int m = 0;
			for (int j = 0; j < o.length; j++) {
				if (o[j] > o[m]) {
					m = j;
				}
			}
			double[] c = substrate.getNode(substrate.size() - 1, m).getCoords();
			m = 0;
			for (int j = 0; j < data[i][1].length; j++) {
				if (data[i][1][j] >= data[i][1][m]) {
					m = j;
				}
			}
			int[] d = new int[2];
			d[1] = m % 11;
			d[0] = (m - d[1])/11;
			double dist =  Math.sqrt(Math.pow(c[0]-d[0],2) + Math.pow(c[1]-d[1],2));
			if (store.containsKey(dist)) {
				int count = store.get(dist);
				store.put(dist, count+1);
			} else {
				store.put(dist, 1);
			}
		}
		
		TreeSet<Double> k = new TreeSet<Double>();
		k.addAll(store.keySet());
		Iterator<Double> iter = k.iterator();
		while(iter.hasNext()) {
			double v = iter.next();
			System.out.println(v+": "+store.get(v));
		}
	}*/
	
	public void testBoxes() {
		int[] x = {50, 350, 650};
		for (int i = 0; i < x.length; i++) {
			boxWrite(training[x[i]][0], "Res/train_input_"+i);
			boxWrite(training[x[i]][1], "Res/train_target_"+i);
			substrate.processInput(training[x[i]][0]);
			boxWrite(substrate.getOutputs(), "Res/train_output_"+i);
			
			/*boxWrite(test[x[i]][0], "test_input_"+i);
			substrate.processInput(test[x[i]][0]);
			boxWrite(substrate.getOutputs(), "test_output_"+i);*/
		}
	}
	
	public void boxWrite(double[] box, String add) {
		double sq = 10;
		String sep = String.format(Locale.US,"%6f",0.0)+",";
		try {
			BufferedWriter w = new BufferedWriter(new FileWriter(add+".csv"));
			for (int i = 0; i < 11*(sq+1)+1; i++) {
				w.write(sep);
			}
			w.write("\n");
			for (int i = 0; i < 11; i++) {
				for (int k = 0; k < sq; k++) {
					w.write(sep);
					for (int j = 0; j < 11; j++) {
						String v = String.format(Locale.US,"%6f",1.0 - box[i*11+j])+",";
						for (int l = 0; l < sq; l++) {
							w.write(v);
						}
						w.write(sep);
					}
					w.write("\n");
				}
				for (int l = 0; l < 11*(sq+1)+1; l++) {
					w.write(sep);
				}
				w.write("\n");
			}
			w.close();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
	
	public TestData[] data() {
		TestData[] r = new TestData[9072];
		int k = 0;
		for (int i = 1; i < 10; i++) {
			for (int j = 1; j < 10; j++) {
				for (int n = 0; n < 11; n++) {
					for (int m = 0; m < 11; m++) {
						//Input
						boolean f = true;
						for (int a = -1; a < 2; a++) {
							for (int b = -1; b < 2; b++) {
								if (i+a == n && j+b == m) {
									f = false;
								}
							}
						}
						if (f) {
							r[k] = new TestData(i-1,j-1,n,m);
							k++;
							if (k > 9071) {
								return r;
							}
						}
					}
				}
			}
		}
		return r;
	}
	
	public double[][][] training(int frac) {
		
		double[] v = new double[10];
		for (int i = 0; i < v.length; i++) {
			v[i] = 1.0 / (1 + Math.exp(i*-0.5 - 5));
      //v[i] = i*0.1;
		}
		double[][] patLarge = {{v[1], v[2], v[3], v[2], v[1]},
								{v[2], v[4], v[6], v[4], v[2]},
								{v[3], v[6], v[9], v[6], v[3]},
								{v[2], v[4], v[6], v[4], v[2]},
								{v[1], v[2], v[3], v[2], v[1]}};
		double[][] patSmall = {{v[1], v[1], v[1]},
								{v[1], v[1], v[1]},
								{v[1], v[1], v[1]}};
    
    
		training = new double[9072/frac][3][121];
		for (int i = 0; i < data.length/frac; i++) {
			for (int j = 0; j < 11; j++) {
				for (int k = 0; k < 11; k++) {
					training[i][0][j*11+k] = data[i*frac].im[j][k];
					training[i][2][j*11+k] = j*11+k;
				}
			}
			//training[i][1][(int) (data[i*frac].cc[0]*11+data[i*frac].cc[1])] = 1;
      /*for (int a = 0; a < 11; a++) {
        for (int b = 0; b < 11; b++) {
          training[i][1][a*11 + b] = v[0];
        }
      }*/
      int x = (int) data[i*frac].cc[0];
      int y = (int) data[i*frac].cc[1];
      for (int a = Math.max(x - 2,0); a <= Math.min(x + 2,10); a++) {
        for (int b = Math.max(y - 2,0); b <= Math.min(y + 2,10); b++) {
          training[i][1][a*11 + b] = patLarge[a-(x-2)][b-(y-2)];
        }
      }
      x = (int) data[i*frac].sm[0];
      y = (int) data[i*frac].sm[1];
      for (int a = Math.max(x - 1,0); a <= Math.min(x + 1,10); a++) {
        for (int b = Math.max(y - 1,0); b <= Math.min(y + 1,10); b++) {
          training[i][1][a*11 + b] = patSmall[a-(x-1)][b-(y-1)];
        }
      }
		}
		return training;
	}

  public void setTraining() {
    for (int i = 0; i < training.length; i++) {
      substrate.processInput(training[i][0]);
      training[i][1] = substrate.getOutputs();
    }
  }

  public void checkTraining() {
    System.out.println("Checking Training");
    for (int i = 0; i < training.length; i++) {
      substrate.processInput(training[i][0]);
      double[] out = substrate.getOutputs();
      for (int j = 0; j < out.length; j++) {
        if (out[j] != training[i][1][j]) 
          System.out.println("Val: "+ i +" "+ j +"\t"+ out[j] +"\t"+ training[i][1][j]);
        if (j != (int) training[i][2][j])
          System.out.println("Idx: "+ i +" "+ j +" "+ training[i][2][j]);
      } 
    }
  }
	
	public double evaluate(NeuralNet net, int xx) {
        HashMap<Double, Integer> store = new HashMap<Double, Integer>();
        HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
        double tot = 0;
    	for (int i = 0; i < data.length; i+=xx) {
    		double[] image = new double[121];
    		for (int j = 0; j < 11; j++) {
    			for (int k = 0; k < 11; k++) {
    				image[j*11+k] = data[i].im[j][k];
    			}
    		}
        int[] c = new int[2];
        double m = Double.NEGATIVE_INFINITY;
        net.processInput(image);
        double[] v = net.getOutputs();
        for (int k = 0; k < v.length; k++) {
            if (v[k] > m) {
                m = v[k];
                c[0] = k/11;
                c[1] = k%11;
            }
        }
        double d = Math.sqrt(Math.pow(data[i].cc[0]-c[0],2) + Math.pow(data[i].cc[1]-c[1],2));
        tot += d/(9072.0/xx);
        if (store.containsKey(d)) {
				int count = store.get(d);
				store.put(d, count+1);
			} else {
				store.put(d, 1);
			}
            if (d == 0) {
            	int l = (int) (data[i].or[0]*3+data[i].or[1]);
            	if (res.containsKey(l)) {
    				int count = res.get(l);
    				res.put(l, count+1);
    			} else {
    				res.put(l, 1);
    			}
            }
    	}
      return tot;
    /*    TreeSet<Double> k = new TreeSet<Double>();
		k.addAll(store.keySet());
		Iterator<Double> iter = k.iterator();
		while(iter.hasNext()) {
			double v = iter.next();
			System.out.println(v+": "+store.get(v));
		}
		System.out.println("Average: "+tot);
		/*tot = 0;
		TreeSet<Integer> x = new TreeSet<Integer>();
		x.addAll(res.keySet());
		Iterator<Integer> x2 = x.iterator();
		while(x2.hasNext()) {
			int v = x2.next();
			tot += res.get(v);
			System.out.println(v+": "+res.get(v));
		}
		System.out.println("Total: "+tot);*/
    }
   

	public double evaluateMax(NeuralNet net) {
        int xx = 1; 
        HashMap<Double, Integer> store = new HashMap<Double, Integer>();
        HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
        double tot = 0;
    	for (int i = 0; i < data.length; i+=xx) {
    		double[] image = new double[121];
    		for (int j = 0; j < 11; j++) {
    			for (int k = 0; k < 11; k++) {
    				image[j*11+k] = data[i].im[j][k];
    			}
    		}
        int[] c = new int[2];
        double m = Double.NEGATIVE_INFINITY;
        net.processInput(image);
        double[] v = net.getOutputs();
        for (int k = 0; k < v.length; k++) {
            if (v[k] > m) {
                m = v[k];
                c[0] = k/11;
                c[1] = k%11;
            }
        }
        double d = Math.sqrt(Math.pow(data[i].cc[0]-c[0],2) + Math.pow(data[i].cc[1]-c[1],2));
        tot += m/(9072.0/xx);
        if (store.containsKey(d)) {
				int count = store.get(d);
				store.put(d, count+1);
			} else {
				store.put(d, 1);
			}
            if (d == 0) {
            	int l = (int) (data[i].or[0]*3+data[i].or[1]);
            	if (res.containsKey(l)) {
    				int count = res.get(l);
    				res.put(l, count+1);
    			} else {
    				res.put(l, 1);
    			}
            }
    	}
      return tot;
    }
  
  public double evaluateMax2(NeuralNet net) {
        int xx = 1; 
        HashMap<Double, Integer> store = new HashMap<Double, Integer>();
        HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
        double tot = 0;
    	for (int i = 0; i < data.length; i+=xx) {
    		double[] image = new double[121];
    		for (int j = 0; j < 11; j++) {
    			for (int k = 0; k < 11; k++) {
    				image[j*11+k] = data[i].im[j][k];
    			}
    		}
        int[] c = new int[2];
        double m = Double.NEGATIVE_INFINITY;
        double m2 = Double.NEGATIVE_INFINITY;
        net.processInput(image);
        double[] v = net.getOutputs();
        for (int k = 0; k < v.length; k++) {
            if (v[k] > m) {
                m2 = m;
                m = v[k];
                c[0] = k/11;
                c[1] = k%11;
            } else if (v[k] > m2) {
                m2 = v[k];
            }
        }
        double d = Math.sqrt(Math.pow(data[i].cc[0]-c[0],2) + Math.pow(data[i].cc[1]-c[1],2));
        tot += m2/(9072.0/xx);
        if (store.containsKey(d)) {
				int count = store.get(d);
				store.put(d, count+1);
			} else {
				store.put(d, 1);
			}
            if (d == 0) {
            	int l = (int) (data[i].or[0]*3+data[i].or[1]);
            	if (res.containsKey(l)) {
    				int count = res.get(l);
    				res.put(l, count+1);
    			} else {
    				res.put(l, 1);
    			}
            }
    	}
      return tot;
    }

   
	  public void evaluateFull(NeuralNet net) {
        int xx = 1;
        HashMap<Double, Integer> store = new HashMap<Double, Integer>();
        HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
        double tot = 0;
    	for (int i = 0; i < data.length; i+=xx) {
    		double[] image = new double[121];
    		for (int j = 0; j < 11; j++) {
    			for (int k = 0; k < 11; k++) {
    				image[j*11+k] = data[i].im[j][k];
    			}
    		}
        int[] c = new int[2];
        double m = Double.NEGATIVE_INFINITY;
        net.processInput(image);
        double[] v = net.getOutputs();
        for (int k = 0; k < v.length; k++) {
            if (v[k] > m) {
                m = v[k];
                c[0] = k/11;
                c[1] = k%11;
            }
        }
        double d = Math.sqrt(Math.pow(data[i].cc[0]-c[0],2) + Math.pow(data[i].cc[1]-c[1],2));
        tot += d/(9072.0/xx);
        if (store.containsKey(d)) {
				int count = store.get(d);
				store.put(d, count+1);
			} else {
				store.put(d, 1);
			}
            if (d == 0) {
            	int l = (int) (data[i].or[0]*3+data[i].or[1]);
            	if (res.containsKey(l)) {
    				int count = res.get(l);
    				res.put(l, count+1);
    			} else {
    				res.put(l, 1);
    			}
            }
    	}
    TreeSet<Double> k = new TreeSet<Double>();
		k.addAll(store.keySet());
		Iterator<Double> iter = k.iterator();
		while(iter.hasNext()) {
			double v = iter.next();
			System.out.println(v+": "+store.get(v));
		}
		System.out.println("Average: "+tot);
		/*tot = 0;
		TreeSet<Integer> x = new TreeSet<Integer>();
		x.addAll(res.keySet());
		Iterator<Integer> x2 = x.iterator();
		while(x2.hasNext()) {
			int v = x2.next();
			tot += res.get(v);
			System.out.println(v+": "+res.get(v));
		}
		System.out.println("Total: "+tot);*/
    }

    private class TestData {
        
        double[][] im = new double[11][11];
        double[][] fr = new double[81][11];
        double[] fc = new double[2]; //Frame coordinate
        double[] co = new double[2]; //Coordinate offset
        double[] or = new double[2]; //Offset rounded
        double[] cc = new double[2]; //Center coordinate
        double[] sm = new double[2]; //Small box
        
        public TestData(int bx, int by, int sx, int sy) {
        	fc[0] = bx;
            fc[1] = by;
            co[0] = (fc[0]/3.0);
            co[1] = (fc[1]/3.0);
            or[0] = 1;//Math.floor(co[0]);
            or[1] = 1;//Math.floor(co[1]);
            cc[0] = fc[0] + or[0];
            cc[1] = fc[1] + or[1];
            sm[0] = sx;
            sm[1] = sy;
        	
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    im[bx+i][by+j] = 1;
                }
            }
            im[sx][sy] = 1;
            
			      for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    for (int k = 0; k < 9; k++) {
                        fr[i*9+j][k] = im[i+(k/3)][j+(k%3)];
                    }
                    //Adding coordinates to each input
                    fr[i*9+j][9] = or[0]-1;//((double)i) / 9.0;
                    fr[i*9+j][10] = or[1]-1;//((double)j) / 9.0;
				        }
			      } 
        }
    }
}
